package com.yuanchu.gateway.config;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yuanchu.common.exception.YuanChuException;
import com.yuanchu.common.utils.R;
import com.yuanchu.gateway.client.FeignAuthClient;
import com.yuanchu.gateway.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.*;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author Mr.M
 * @version 1.0
 * @description 网关认证过虑器
 * @date 2022/9/27 12:10
 */
@Component
@Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered {

    @Value("${yuanchu.security.client-id}")
    private String clientId;

    @Value("${yuanchu.security.client-secret}")
    private String clientSecret;

    @Resource
    private TokenStore tokenStore;
    @Resource
    private RedisUtils redisUtils;

    @Resource
    private RestTemplate restTemplate;
    @Resource
    private FeignAuthClient feignAuthClient;



    //白名单
    private static List<String> whitelist = null;

    static {
        //加载白名单
        try (
                InputStream resourceAsStream = GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties");
        ) {
            Properties properties = new Properties();
            properties.load(resourceAsStream);
            Set<String> strings = properties.stringPropertyNames();
            whitelist= new ArrayList<>(strings);

        } catch (Exception e) {
            log.error("加载/security-whitelist.properties出错:{}",e.getMessage());
            e.printStackTrace();
        }


    }



    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String requestUrl = exchange.getRequest().getPath().value();
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        AntPathMatcher pathMatcher = new AntPathMatcher();
        //白名单放行
        for (String url : whitelist) {
            if (pathMatcher.match(url, requestUrl)) {
                return chain.filter(exchange);
            }
        }

        //检查token是否存在
        String token = getToken(exchange);
        if (StringUtils.isBlank(token)) {
            return buildReturnMono("请先登入",exchange);
        }
        //判断是否是有效的token
        OAuth2AccessToken oAuth2AccessToken;
        try {
            oAuth2AccessToken = tokenStore.readAccessToken(token);

            boolean expired = oAuth2AccessToken.isExpired();
            // boolean expired = isTokenAboutToExpire(oAuth2AccessToken);
            if (expired) {
                // 从redis获取refresh_token
                /*String refresh_token = (String) redisUtils.get("token_" + token);
                if (StringUtils.isEmpty(refresh_token)) {
                    // 令牌不存在，重新登入
                    // 如果refresh_token也已过期，则清除令牌并返回401状态码
                    clearTokensFromRedisAndResponse(exchange);
                    // return chain.filter(exchange);
                    return buildReturnMono("令牌过期，请重新登入",exchange);
                }
                // 刷新令牌
                String new_token = refreshAccessToken(refresh_token, token);
                // 将新令牌添加到请求头中
                HttpHeaders headers = response.getHeaders();
                // refreshOAuth2Token(refresh_token);
                //设置为可修改的
                headers= HttpHeaders.writableHttpHeaders(headers);
                //设置请求头
                headers.set(HttpHeaders.AUTHORIZATION, "Bearer " +new_token);
                headers.set("Access-Token",new_token);
                // headers.setBearerAuth(new_token);
                // 创建带有新令牌的新请求
                // ServerHttpRequest newRequest = (ServerHttpRequest) exchange.mutate().request(request.mutate().headers(h -> h.addAll(headers)).build()).build();
                // return chain.filter(exchange);
                Map<String,Object> map=new HashMap<>();
                map.put("token",new_token);
                map.put("status", 2000);*/

                // return R.error().put("data", map);
                return buildReturnMono("token 过期",exchange);
            }
            return chain.filter(exchange);
        } catch (InvalidTokenException e) {
            log.info("认证令牌无效: {}", token);
            return buildReturnMono("认证令牌无效",exchange);
        }

    }
    private boolean isTokenAboutToExpire(OAuth2AccessToken token) {
        // 自定义方法检查token是否接近过期
        long expiresInMilliseconds = token.getExpiresIn() * 1000; // 将秒转换为毫秒
        long currentTimeMillis = System.currentTimeMillis();
        // 判断token是否即将过期
        boolean isTokenAboutToExpire = expiresInMilliseconds - currentTimeMillis < 60 * 1000; // 假设在过期前1分钟内刷新
        return isTokenAboutToExpire;
    }


    public String refreshOAuth2Token(String refreshToken) {
        // 构建请求体
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.setBasicAuth(clientId, clientSecret);

        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("grant_type", "refresh_token");
        params.add("refresh_token", refreshToken);

        // 发送POST请求
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
        try {
            ResponseEntity<OAuth2AccessToken> response = restTemplate.postForEntity(
                    "http://yuanchu-auth/oauth/token",
                    request,
                    OAuth2AccessToken.class
            );

            // 检查响应状态码
            if (response.getStatusCode().is2xxSuccessful()) {
                // 存储新的refresh_token到Redis（假设键名为"refresh_token:<username>"）
                OAuth2AccessToken accessToken = response.getBody();
                redisUtils.set("refresh_token:" + accessToken.getTokenType(), accessToken.getRefreshToken().getValue());
                return accessToken.getTokenType();
            } else {
                throw new YuanChuException("刷新令牌失败。响应状态代码" + response.getStatusCode());
            }
        } catch (Exception ex) {
            // 捕捉并处理异常
            log.error("刷新令牌时出错", ex);
            throw ex;
        }

    }


    private void clearTokensFromRedisAndResponse(ServerWebExchange exchange) {
        // 移除请求头中的Authorization信息
        // ServerHttpRequest request = exchange.getRequest().mutate()
        //         .headers(headers -> headers.remove(HttpHeaders.AUTHORIZATION)).build();
        // exchange.mutate().request(request).build();
        String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");
        String accessToken =  exchange.getRequest().getHeaders().getFirst("Access-Token");

        OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(accessToken);
        if (oAuth2AccessToken != null) {
            OAuth2RefreshToken oAuth2RefreshToken = oAuth2AccessToken.getRefreshToken();
            //从tokenStore中移除token
            tokenStore.removeAccessToken(oAuth2AccessToken);
            // 移除refresh_token
            if (oAuth2RefreshToken != null) {
                tokenStore.removeRefreshToken(oAuth2RefreshToken);
            }
        }
    }
    /**
     * 刷新token
     */
    private String refreshAccessToken(String refresh_token, String token) {

        ///auth/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=refresh_token&
        //     refresh_token=
        MultiValueMap<String, String> paramsMap = new LinkedMultiValueMap<>();
        paramsMap.set("client_id", clientId);
        paramsMap.set("client_secret", clientSecret);
        paramsMap.set("grant_type", "refresh_token");
        paramsMap.set("refresh_token", refresh_token);
        Map<String, String> accessToken = null;
        try {
            accessToken = feignAuthClient.refreshAccessToken(paramsMap);
        } catch (Exception e) {
            log.error("获取token失败：{}", e.getMessage());
            String message = e.getMessage();
            // 找到JSON并提取错误信息开始的位置
            int jsonStart = message.lastIndexOf("[") + 1;
            int jsonEnd = message.lastIndexOf("]");
            String jsonResponse = message.substring(jsonStart, jsonEnd);
            // 使用Jackson解析JSON
            ObjectMapper objectMapper = new ObjectMapper();
            Map<String, String> errorInfo = null;
            try {
                errorInfo = objectMapper.readValue(jsonResponse, new TypeReference<Map<String, String>>() {
                });
            } catch (JsonProcessingException ex) {
                // JSON解析失败
                log.error("JSON解析失败：{}", ex.getMessage());
                throw new YuanChuException(ex.getMessage());
            }
            throw new YuanChuException(errorInfo.get("刷新令牌失败"));
        }

        String new_token = accessToken.get("access_token");
        String new_refresh_token = accessToken.get("refresh_token");
        redisUtils.set("token_" + new_token, new_refresh_token, 604800);
        redisUtils.del("token_" + token);
        return new_token;
    }

    /**
     * 获取token
     */
    private String getToken(ServerWebExchange exchange) {
        String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");


        if (StringUtils.isBlank(tokenStr)) {
            return null;
        }
        if ("Bearer false".equals(tokenStr)) {
            return null;
        }
        String token = tokenStr.split(" ")[1];
        if (StringUtils.isBlank(token)) {
            return null;
        }
        return token;
    }




    private Mono<Void> buildReturnMono(String error, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();

//        String jsonString = JSON.toJSONString(new RestErrorResponse(error));
        String jsonString = JSON.toJSONString(R.error().put("data", error));
        byte[] bits = jsonString.getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    private Mono<Void> buildReturnMono(Map<String, Object> map, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();

//        String jsonString = JSON.toJSONString(new RestErrorResponse(error));
        String jsonString = JSON.toJSONString(R.error().put("data", map));
        byte[] bits = jsonString.getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }
    @Override
    public int getOrder() {
        return 0;
    }
}
